﻿using LightCAD.MathLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static LightCAD.MathLib.MathEx;

namespace LightCAD.Three
{
        public static class HalfUtils
        {
            public class table
            {
                public float[] floatView;
                public UInt32[] uint32View;
                public UInt32[] baseTable;
                public UInt32[] shiftTable;
                public UInt32[] mantissaTable;
                public UInt32[] exponentTable;
                public UInt32[] offsetTable;
            }
            private static readonly table _tables= _generateTables();
            private static table _generateTables()
            {
                // float32 to float16 helpers

                var floatView = new float[1];
                var uint32View = new UInt32[1];

                var baseTable = new UInt32[512];
                var shiftTable = new UInt32[512];

                for (var i = 0; i < 256; ++i)
                {

                    var e = i - 127;

                    // very small number (0, -0)

                    if (e < -27)
                    {

                        baseTable[i] = 0x0000;
                        baseTable[i | 0x100] = 0x8000;
                        shiftTable[i] = 24;
                        shiftTable[i | 0x100] = 24;

                        // small number (denorm)

                    }
                    else if (e < -14)
                    {

                        baseTable[i] =(UInt32)( 0x0400 >> (-e - 14));
                        baseTable[i | 0x100] = (UInt32)((0x0400 >> (-e - 14)) | 0x8000);
                        shiftTable[i] = (UInt32)(-e - 1);
                        shiftTable[i | 0x100] = (UInt32)(-e - 1);

                        // normal number

                    }
                    else if (e <= 15)
                    {

                        baseTable[i] = (UInt32)((e + 15u) << 10);
                        baseTable[i | 0x100] = (UInt32)(((e + 15) << 10) | 0x8000);
                        shiftTable[i] = 13;
                        shiftTable[i | 0x100] = 13;

                        // large number (Infinity, -Infinity)

                    }
                    else if (e < 128)
                    {

                        baseTable[i] = 0x7c00;
                        baseTable[i | 0x100] = 0xfc00;
                        shiftTable[i] = 24;
                        shiftTable[i | 0x100] = 24;

                        // stay (NaN, Infinity, -Infinity)

                    }
                    else
                    {

                        baseTable[i] = 0x7c00;
                        baseTable[i | 0x100] = 0xfc00;
                        shiftTable[i] = 13;
                        shiftTable[i | 0x100] = 13;

                    }

                }

                // float16 to float32 helpers

                var mantissaTable = new UInt32[2048];
                var exponentTable = new UInt32[64];
                var offsetTable = new UInt32[64];

                for (var i = 1; i < 1024; ++i)
                {

                    var m = i << 13; // zero pad mantissa bits
                    var e = 0; // zero exponent

                    // normalized
                    while ((m & 0x00800000) == 0)
                    {

                        m <<= 1;
                        e -= 0x00800000; // decrement exponent

                    }

                    m &= ~0x00800000; // clear leading 1 bit
                    e += 0x38800000; // adjust bias

                    mantissaTable[i] =(UInt32)( m | e);

                }

                for (var i = 1024; i < 2048; ++i)
                {

                    mantissaTable[i] =(UInt32)( 0x38000000 + ((i - 1024) << 13));

                }

                for (var i = 1; i < 31; ++i)
                {

                    exponentTable[i] =(UInt32)( i << 23);

                }

                exponentTable[31] = 0x47800000;
                exponentTable[32] = 0x80000000;

                for (var i = 33; i < 63; ++i)
                {

                    exponentTable[i] = (UInt32)(0x80000000 + ((i - 32) << 23));

                }

                exponentTable[63] = 0xc7800000;

                for (var i = 1; i < 64; ++i)
                {

                    if (i != 32)
                    {

                        offsetTable[i] = 1024;

                    }

                }

                return new table
                {
                    floatView = floatView,
                    uint32View = uint32View,
                    baseTable = baseTable,
                    shiftTable = shiftTable,
                    mantissaTable = mantissaTable,
                    exponentTable = exponentTable,
                    offsetTable = offsetTable

                };

            }
            // float32 to float16
            public static ushort toHalfFloat(double value)
            {
                if (Math.Abs(value) > 65504) console.warn("THREE.DataMathEx.toHalfFloat(): Value out of range.");
                var val = (float)Clamp(value, -65504, 65504);
                //_tables.floatView[0] = val;
                var buffer = BitConverter.GetBytes(val);
                var f = BitConverter.ToUInt32(buffer, 0);//_tables.uint32View[0];
                var e = (f >> 23) & 0x1ff;
                return Convert.ToUInt16(_tables.baseTable[e] + ((f & 0x007fffff) >> (int)_tables.shiftTable[e]));
            }
            //public static ushort toHalfFloat1(float value)
            //{
            //    var bytes = BitConverter.GetBytes(value);
            //    uint fltInt32 = BitConverter.ToUInt32(bytes, 0);
            //    var fltInt16 = (ushort)((fltInt32 & 0x7fffffff) >> 13) - (0x38000000 >> 13);
            //    fltInt16 |= (ushort)((fltInt32 & 0x80000000) >> 16);
            //    return (ushort)fltInt16;
            //}

            // float16 to float32

            public static float fromHalfFloat(ushort val)
            {
                var m = val >> 10;
               var uintVal=_tables.mantissaTable[_tables.offsetTable[m] + (val & 0x3ff)] + _tables.exponentTable[m];
                var buffer = BitConverter.GetBytes(uintVal);
                //return _tables.floatView[0];
                return BitConverter.ToSingle(buffer, 0);
            }
            //public static float fromHalfFloat1(ushort value)
            //{
            //    int fltInt32 = ((value & 0x8000) << 16);
            //    fltInt32 |= ((value & 0x7fff) << 13) + 0x38000000;

            //    var bytes = BitConverter.GetBytes(fltInt32);
            //    return BitConverter.ToSingle(bytes, 0);
            //}
        }
}
